#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <assert.h>
#include <string.h>
#include <ctype.h>
#include <fcntl.h>
#include <errno.h>

#define NUM 1024
#define OPT_NUM 64

#define NONE_REDIR 0
#define INPUT_REDIR 1
#define OUTPUT_REDIR 2
#define APPEND_REDIR 3

#define trimSpace(start) do{\
            while(isspace(*start))\
                ++start;\
            }while(0)

char command_line_array[NUM];
char *myargv[OPT_NUM];//指针数组，每个指针指向命令行被切割后的字符串
int lastcode=0;
int lastsig=0;
int redirType;
char* redirFile;

void commandCheck(char* command_line_array)
{
    assert(command_line_array);
    char* start = command_line_array;
    char* end = command_line_array + strlen(command_line_array);//end指向\0
    
    while(start < end)
    {
        if(*start == '>')
        {
            *start = '\0';
            start++;
            if(*start == '>')
            {
                redirType = APPEND_REDIR;
                start++;
            }
            else 
            {
                redirType = OUTPUT_REDIR;
            }
            trimSpace(start);
            redirFile = start;
            break;
        }
        else if(*start == '<')
        {
            *start = '\0';
            start++;
            trimSpace(start);//过滤重定向符号后面的空格
            //填写重定向信息
            redirType = INPUT_REDIR;
            redirFile = start;
            break;
        }
        else 
        {
             start++;//如果没有重定向符号，会进入else分支语句，start一直++，while循环最后停止
        }
    }


}
int main()
{   
    while(1)
    {
        redirType = NONE_REDIR;
        redirFile = NULL;
        errno = 0;//重新执行命令时，保证这些数据都被初始化。

        printf("[%s@%s 当前路径]#",getenv("USER"),getenv("HOSTNAME"));
        //获取用户输入
        char *s=fgets(command_line_array,sizeof(command_line_array)-1,stdin);//读取字节数最大为1023留出一个\0
        assert(s!=NULL);
        //将获取输入时输入的回车赋值成反斜杠0
        command_line_array[strlen(command_line_array)-1] = 0;
        
        //将命令行输入的字符串，进行字符串切割，以空格为分隔符
        //空格全都换成反斜杠0，或者用strtok
        // "ls -a -l -i" > "log.txt"
        // "cat" < "log.txt" 
        // "ls -a -l -i" >> "log.txt"
        
        //在命令字符串切割之前，首先需要以重定向符号为基准将命令行切割为目标文件和执行命令两部分,把重定向符号赋值为\0即可
        commandCheck(command_line_array);

        myargv[0]=strtok(command_line_array," ");
        int i=1;
        if(strcmp(myargv[0],"ls") == 0 && myargv[0]!= NULL)//我们自己在ls的命令行参数表中手动加上执行颜色命令。
        {
            myargv[i++]=(char*)"--color=auto";
        }
        
        while(myargv[i++]=strtok(NULL," "));
        
        // 如果是cd命令，不需要创建子进程，让shell进程执行cd命令就可以，本质就是执行系统接口chdir
        // 像这种不需要派生子进程执行，而是让shell自己执行的命令，我们称之为内建或内置命令。
        if(myargv[0] != NULL && strcmp(myargv[0],"cd")==0)
        {
            if(myargv[1] != NULL)
            {
                chdir(myargv[1]);//将shell进程的工作目录改为cd的路径
                continue;
            }
        }
        // 完成另一个内建命令echo的运行，保证$?可以运行
        if(myargv[0]!=NULL && myargv[1]!=NULL && strcmp(myargv[0],"echo")==0)
        {
            if(strcmp(myargv[1],"$?") == 0)
            {
                printf("%d，%d\n",lastcode,lastsig);
            }
            else
            {
                printf("%s\n",myargv[1]);
            }
            continue;//后面的代码无须继续执行，直接continue即可
        }

        // 最后以NULL结尾，切割的字符串中已经没有字符串时，函数返回NULL
#ifdef DEBUG 
        for(int i=0;myargv[i],i++)
        {
            printf("myargv[%d]:%s\n",myargv[i]);
        }
#endif 
        //执行命令
        pid_t id=fork();
        assert(id!=-1);
        if(id==0)
        {
            //子进程进行重定向
            switch(redirType)
            {
                case NONE_REDIR:
                    //什么都不做即可
                    break;
                case INPUT_REDIR:
                    {
                        int fd = open(redirFile,O_RDONLY);
                        if(fd < 0)
                        {
                            perror("open");
                            exit(errno);//文件打开失败，命令执行出现错误，没必要进行子进程的程序替换，直接终止子进程即可。
                        }
                        //重定向的文件已经成功打开
                        dup2(fd,0);
                    }
                    break;
                case APPEND_REDIR:
                case OUTPUT_REDIR:
                    {
                        umask(0000);
                        int flags = O_WRONLY | O_CREAT;
                        if(redirType == APPEND_REDIR) flags |= O_APPEND;
                        else flags |= O_TRUNC;
                        int fd = open(redirFile,flags,0666);
                        if(fd < 0)
                        {
                            perror("open");
                            exit(errno);//文件打开失败，命令执行错误，终止子进程。
                        }
                        //重定向的文件已经成功打开
                        dup2(fd,1);
                    }
                    break;
                default:
                    printf("bug?");//重定向只设置了4种类型，现在出现第5种，可能出现了bug
                    break;
            }
            
            
            execvp(myargv[0],myargv);
            exit(1);//如果程序替换失败，直接让子进程退出
        }
        int status=0;
        pid_t ret = waitpid(id,&status,0);
        assert(ret > 0);
        lastcode = ((status>>8) & 0xFF);
        lastsig = (status & 0x7F);
        
    }
    return 0;
}


















